1 /* 2 * Hunt - a framework for web and console application based on Collie using Dlang development 3 * 4 * Copyright (C) 2015-2017 Shanghai Putao Technology Co., Ltd 5 * 6 * Developer: HuntLabs 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.application.controller; 13 14 import kiss.logger; 15 16 public import hunt.view; 17 public import hunt.http.response; 18 public import hunt.http.request; 19 public import hunt.routing; 20 public import hunt.application.middleware; 21 22 import hunt.cache; 23 import hunt.application.application; 24 25 import std.exception; 26 import std.traits; 27 import std.string; 28 29 enum Action; 30 31 struct Middleware 32 { 33 string className; 34 } 35 36 37 abstract class Controller 38 { 39 protected 40 { 41 Request request; 42 ///called before all actions 43 IMiddleware[] middlewares; 44 View _view; 45 } 46 final @property session() 47 { 48 return request.getSession(); 49 } 50 final @property response() 51 { 52 return request.createResponse(); 53 } 54 /// called before action return true is continue false is finish 55 // bool before(){return true;} 56 bool before() 57 { 58 /** 59 CORS support 60 http://www.cnblogs.com/feihong84/p/5678895.html 61 https://stackoverflow.com/questions/10093053/add-header-in-ajax-request-with-jquery 62 */ 63 64 // FIXME: Needing refactor or cleanup -@zxp at 5/10/2018, 11:33:11 AM 65 // set this through the configuration 66 response.setHeader("Access-Control-Allow-Origin", "*"); 67 response.setHeader("Access-Control-Allow-Methods", "*"); 68 response.setHeader("Access-Control-Allow-Headers", "*"); 69 70 if (toUpper(request.method) == "OPTIONS") 71 { 72 return false; 73 } 74 75 return true; 76 } 77 78 /// called after action return true is continue false is finish 79 bool after(){return true;} 80 81 ///add middleware 82 ///return true is ok, the named middleware is already exist return false 83 bool addMiddleware(IMiddleware m) 84 { 85 if(m is null) return false; 86 foreach(tmp; this.middlewares) 87 { 88 if(tmp.name == m.name) 89 { 90 return false; 91 } 92 } 93 94 this.middlewares ~= m; 95 return true; 96 } 97 98 // get all middleware 99 IMiddleware[] getMiddlewares() 100 { 101 return this.middlewares; 102 } 103 104 //view render 105 @property View view() 106 { 107 if(_view is null) 108 { 109 _view = new View(); 110 } 111 return _view; 112 } 113 114 @property UCache cache() 115 { 116 return Application.getInstance().getCache(); 117 } 118 119 @property cacheManger() 120 { 121 return Application.getInstance().getCacheManger(); 122 } 123 124 void render(string filename = null)() 125 { 126 this.response.html(this.view.render!filename()); 127 } 128 alias show = render; 129 130 void done() 131 { 132 this.response.done(); 133 } 134 135 protected final bool doMiddleware() 136 { 137 foreach(m; middlewares) 138 { 139 logDebugf("do %s onProcess ..", m.name()); 140 141 auto response = m.onProcess(this.request, this.response); 142 if(response is null) 143 { 144 continue; 145 } 146 147 logDebugf("Middleware %s is retrun done.", m.name); 148 response.done(); 149 return false; 150 } 151 152 return true; 153 } 154 155 @property bool isAsync() 156 { 157 return true; 158 } 159 } 160 161 mixin template MakeController(string moduleName = __MODULE__) 162 { 163 mixin HuntDynamicCallFun!(typeof(this),moduleName); 164 } 165 166 mixin template HuntDynamicCallFun(T,string moduleName) 167 { 168 public: 169 mixin(__createCallActionFun!(T,moduleName)); 170 shared static this() 171 { 172 mixin(__creteRouteMap!(T,moduleName)); 173 } 174 } 175 176 string __createCallActionFun(T, string moduleName)() 177 { 178 import std.traits; 179 import std.format; 180 181 string str = "bool callAction(string funName, Request req) {"; 182 str ~= "\n\tauto ptr = this; ptr.request = req;"; 183 str ~= "\n\tswitch(funName){"; 184 foreach(memberName; __traits(allMembers, T)) 185 { 186 static if (is(typeof(__traits(getMember, T, memberName)) == function) ) 187 { 188 foreach (t;__traits(getOverloads,T,memberName)) 189 { 190 //alias pars = ParameterTypeTuple!(t); 191 static if(/*ParameterTypeTuple!(t).length == 0 && */( hasUDA!(t, Action) || hasUDA!(t, Route))) 192 { 193 str ~= "case \""; 194 str ~= memberName; 195 str ~= "\": {\n"; 196 static if(hasUDA!(t, Action)) 197 { 198 enum middlewares = getUDAs!(t, Middleware); 199 static if(middlewares.length) 200 { 201 foreach(i, middleware; middlewares) 202 { 203 str ~= format("ptr.addMiddleware(new %s);", middleware.className); 204 } 205 } 206 207 str ~= "if(!ptr.doMiddleware()){return false;}"; 208 209 //before 210 str ~= q{ 211 if(!ptr.before()){return false;} 212 }; 213 } 214 215 //action 216 str ~= "ptr." ~ memberName ~ "();"; 217 218 static if(hasUDA!(t, Action)){ 219 //after 220 str ~= q{ 221 if(!ptr.after()){return false;} 222 }; 223 } 224 str ~= "}\n break;"; 225 } 226 } 227 } 228 } 229 230 str ~= "default : break;}"; 231 str ~= "return false;"; 232 str ~= "}"; 233 234 return str; 235 } 236 237 string __creteRouteMap(T, string moduleName)() 238 { 239 string str = ""; 240 241 //pragma(msg, "moduleName", moduleName); 242 str ~= "\n\timport hunt.application.staticfile;\n"; 243 str ~= "\n\taddRouteList(\"hunt.application.staticfile.StaticfileController.doStaticFile\", &callHandler!(StaticfileController, \"doStaticFile\"));\n"; 244 245 foreach(memberName; __traits(allMembers, T)) 246 { 247 static if (is(typeof(__traits(getMember, T, memberName)) == function)) 248 { 249 foreach (t;__traits(getOverloads, T, memberName)) 250 { 251 static if (/*ParameterTypeTuple!(t).length == 0 && */ hasUDA!(t, Action)) 252 { 253 str ~= "\n\taddRouteList(\"" ~ moduleName ~ "." ~ T.stringof ~ "." ~ memberName ~ "\",&callHandler!(" ~ T.stringof ~ ",\"" ~ memberName ~ "\"));\n"; 254 } 255 } 256 } 257 } 258 259 return str; 260 } 261 262 void callHandler(T, string fun)(Request req) if(is(T == class) || is(T == struct) && hasMember!(T,"__CALLACTION__")) 263 { 264 T handler = new T(); 265 266 import core.memory; 267 scope(exit){if(!handler.isAsync){handler.destroy(); GC.free(cast(void *)handler);}} 268 269 //handler.before(); // It's already been called in line 183. 270 req.action = fun; 271 handler.callAction(fun, req); 272 handler.after(); // Although the line 193 also has the code that calls after, but where has not executed, so this reservation 273 handler.done(); 274 } 275 276 HandleFunction getRouteFormList(string str) 277 { 278 if (!_init) 279 { 280 _init = true; 281 } 282 283 return __routerList.get(str, null); 284 } 285 286 void addRouteList(string str, HandleFunction fun) 287 { 288 //trace("add str is .... ", str); 289 if(!_init) 290 { 291 import std.string : toLower; 292 293 __routerList[str.toLower] = fun; 294 } 295 } 296 297 private: 298 __gshared bool _init = false; 299 __gshared HandleFunction[string] __routerList;